package com.turing.common.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import com.hierynomus.msdtyp.AccessMask;
import com.hierynomus.msfscc.FileAttributes;
import com.hierynomus.msfscc.fileinformation.FileIdBothDirectoryInformation;
import com.hierynomus.mssmb2.SMB2CreateDisposition;
import com.hierynomus.mssmb2.SMB2CreateOptions;
import com.hierynomus.mssmb2.SMB2ShareAccess;
import com.hierynomus.smbj.SMBClient;
import com.hierynomus.smbj.SmbConfig;
import com.hierynomus.smbj.auth.AuthenticationContext;
import com.hierynomus.smbj.connection.Connection;
import com.hierynomus.smbj.session.Session;
import com.hierynomus.smbj.share.DiskShare;
import com.turing.common.entity.SmbInfo;
import com.turing.common.spring.SpringApplicationContext;

/**
 * @Description smb工具类
 * @Author lcy
 * @Date 2021/5/17 23:00
 */
public class SmbUtil {

    /**
     * @Description 自定义接口。校验smb一些特殊场景使用
     * @Author lcy
     * @Date 2021/5/17 23:00
     */
    public interface SmbBusiness {

        /**
         * 校验远程服务器对象是否符合
         *
         * @param serverFile 远程服务器对象
         * @author lcy
         * @date 2021/5/18 13:42
         **/
        boolean checkFile(FileIdBothDirectoryInformation serverFile);
    }

    /**
     * smb配置信息对象
     */
    private static SmbInfo smbInfo;

    /**
     * 缓存文件路径前缀
     */
    public static final String LOCAL_PATH_PREFIX = "/tmp/file/";

    /**
     * 初始化smb配置类对象
     *
     * @param smbInfo smb信息对象
     * @author lcy
     * @date 2021/5/17 23:45
     **/
    private synchronized static SmbConfig initSmbConfig(SmbInfo smbInfo) {
        return SmbConfig.builder().withReadBufferSize(smbInfo.getReadBufferSize())
                .withTimeout(smbInfo.getTimeout(), TimeUnit.SECONDS)
                .withReadTimeout(smbInfo.getReadTimeout(), TimeUnit.SECONDS)
                .withSoTimeout(smbInfo.getSocketTimeout(), TimeUnit.SECONDS)
                .build();
    }

    /**
     * 初始化smb信息对象
     *
     * @author lcy
     * @date 2021/5/17 23:45
     **/
    private synchronized static void initSmbInfo() {
        //从配置文件中读取
        if (smbInfo == null) {
            smbInfo = SpringApplicationContext.getBean(SmbInfo.class);
        }
    }

    /**
     * 下载远程远程服务器文件
     *
     * @param path 路径--不以/或者\\开头，但是以/或者\\结尾结尾
     * @return java.util.List<java.io.File>
     * @author lcy
     * @date 2021/5/17 23:46
     **/
    public static List<File> downloadFileBySmb2(String path) {
        return downloadFileBySmb2(path, "*");
    }

    /**
     * 下载远程远程服务器文件
     *
     * @param path          路径--不以/或者\\开头，但是以/或者\\结尾结尾
     * @param searchPattern 匹配的格式,如匹配pdf:*.pdg,也可以匹配到具体的文件
     * @return java.util.List<java.io.File>
     * @author lcy
     * @date 2021/5/17 23:46
     **/
    public static List<File> downloadFileBySmb2(String path, String searchPattern) {
        //初始化smb信息配置
        initSmbInfo();
        return downloadFileBySmb2(smbInfo, path, searchPattern, file -> true);
    }

    /**
     * 下载指定远程远程服务器文件，下载根目录+搜索目录的所有文件。需要手动删除返回的临时文件
     *
     * @param smbInfo       smb信息对象
     * @param path          服务器路径--不以/或者\\开头，但是以/或者\\结尾结尾
     * @param searchPattern 匹配的格式默认匹配所有,如匹配pdf:*.pdg
     * @param smbBusiness   业务接口实现，一些特殊业务校验规则
     * @return java.util.List<java.io.File>
     * @author lcy
     * @date 2021/5/17 23:46
     **/
    public static List<File> downloadFileBySmb2(SmbInfo smbInfo, String path, String searchPattern, SmbBusiness smbBusiness) {
        //将windows文件符转成linux
        path = path.replace("\\\\", "/");
        path = path.replace("\\", "/");
        int index = path.indexOf("/");
        if (index == 0) {
            path = path.substring(1);
        }
        //将路径拆分
        index = path.indexOf("/");
        //共享根路径
        String rootDir;
        //需要查找的子目录
        String searchDir;
        if (index == -1) {
            rootDir = path;
            searchDir = "";
        } else {
            rootDir = path.substring(0, index);
            searchDir = index == path.length() - 1 ? "" : path.substring(index + 1);
        }

        if (searchDir.length() - 1 != searchDir.lastIndexOf("/")) {
            searchDir = searchDir + "/";
        }
        //初始化配置
        SmbConfig smbConfig = initSmbConfig(smbInfo);

        List<File> fileList = null;
        try (SMBClient smbClient = new SMBClient(smbConfig)) {
            //获取连接
            Connection connection = smbClient.connect(smbInfo.getHost());
            AuthenticationContext ac = new AuthenticationContext(smbInfo.getUsername(), smbInfo.getPassword().toCharArray(), smbInfo.getDomain());
            Session session = connection.authenticate(ac);
            // 连接共享文件夹
            DiskShare share = (DiskShare) session.connectShare(rootDir);
            if (Tools.isEmpty(searchPattern)) {
                searchPattern = "*";
            }
            //创建缓存文件夹目录
            String localPath = LOCAL_PATH_PREFIX + LocalDate.now().format(DateTimeFormatter.ofPattern(DateUtils.DATE_NOT_LINE)) + File.separator;
            File localParentFile = new File(localPath);
            if (!localParentFile.exists()) {
                localParentFile.mkdirs();
            }

            List<FileIdBothDirectoryInformation> list = share.list(searchDir, searchPattern);
            fileList = new ArrayList<>(list.size());
            for (FileIdBothDirectoryInformation serverFile : list) {

                String fileName = serverFile.getFileName();
                String filePath = searchDir + fileName;
                String dstPath = localPath + fileName;
                //校验文件名称，校验业务逻辑，校验文件的真实性
                if (FileAttributes.FILE_ATTRIBUTE_ARCHIVE.getValue() != serverFile.getFileAttributes() || !smbBusiness.checkFile(serverFile) || !share.fileExists(filePath)) {
                    continue;
                }

                File file = new File(dstPath);
                com.hierynomus.smbj.share.File smbFileRead = share.openFile(filePath, EnumSet.of(AccessMask.GENERIC_READ), null, SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OPEN, null);
                downloadFile(fileList, file, smbFileRead, rootDir + "/" + filePath);
            }
        } catch (Exception e) {
            Log.error("连接并搜索共享文件夹失败。host = {},path = {} ", smbInfo.getHost(), path, e);
        }
        return fileList;
    }

    /**
     * 查找路径下的子目录
     *
     * @param smbInfo     smb信息对象
     * @param path        服务器路径--不以/或者\\开头，但是以/或者\\结尾结尾
     * @param smbBusiness 业务接口实现，一些特殊业务校验规则
     * @return java.util.List<java.lang.String>
     * @author lcy
     * @date 2021/5/17 23:46
     **/
    public static List<String> searchChildFile(SmbInfo smbInfo, String path, SmbBusiness smbBusiness) {
        //将windows文件符转成linux
        path = path.replace("\\\\", "/");
        path = path.replace("\\", "/");
        int index = path.indexOf("/");
        if (index == 0) {
            path = path.substring(1);
        }
        //将路径拆分
        index = path.indexOf("/");
        //共享根路径
        String rootDir;
        //需要查找的子目录
        String searchDir;
        if (index == -1) {
            rootDir = path;
            searchDir = "";
        } else {
            rootDir = path.substring(0, index);
            searchDir = index == path.length() - 1 ? "" : path.substring(index + 1);
        }

        if (searchDir.length() - 1 != searchDir.lastIndexOf("/")) {
            searchDir = searchDir + "/";
        }
        //初始化配置
        SmbConfig smbConfig = initSmbConfig(smbInfo);

        List<String> fileList = null;
        try (SMBClient smbClient = new SMBClient(smbConfig)) {
            //获取连接
            Connection connection = smbClient.connect(smbInfo.getHost());
            AuthenticationContext ac = new AuthenticationContext(smbInfo.getUsername(), smbInfo.getPassword().toCharArray(), smbInfo.getDomain());
            Session session = connection.authenticate(ac);
            // 连接共享文件夹
            DiskShare share = (DiskShare) session.connectShare(rootDir);

            //创建缓存文件夹目录
            String localPath = LOCAL_PATH_PREFIX + LocalDate.now().format(DateTimeFormatter.ofPattern(DateUtils.DATE_NOT_LINE)) + File.separator;
            File localParentFile = new File(localPath);
            if (!localParentFile.exists()) {
                localParentFile.mkdirs();
            }

            List<FileIdBothDirectoryInformation> list = share.list(searchDir, "*");
            fileList = new ArrayList<>(list.size());
            for (FileIdBothDirectoryInformation serverFile : list) {

                String fileName = serverFile.getFileName();
                //如果是非文件夹，返回
                if ("..".equals(fileName) || ".".equals(fileName) || FileAttributes.FILE_ATTRIBUTE_DIRECTORY.getValue() != serverFile.getFileAttributes()) {
                    continue;
                }
                //校验文件名称，校验业务逻辑，校验文件的真实性
                if (!smbBusiness.checkFile(serverFile)) {
                    continue;
                }
                fileList.add(fileName);

            }
        } catch (Exception e) {
            Log.error("连接并搜索共享文件夹失败。host = {},path = {} ", smbInfo.getHost(), path, e);
        }
        return fileList;
    }

    /**
     * 根据修改日期下载指定远程远程服务器文件里的文件
     *
     * @param smbInfo       smb配置对象
     * @param path          路径
     * @param searchPattern 匹配的格式,如匹配pdf:*.pdg
     * @param localDate     修改日期
     * @return java.util.List<java.io.File>
     * @author lcy
     * @date 2021/5/17 23:46
     **/
    public static List<File> downloadByUpdateTime(SmbInfo smbInfo, String path, String searchPattern, LocalDate localDate) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return downloadFileBySmb2(smbInfo, path, searchPattern, serverFile -> {
            //校验最后修改日期
            Date lastUpdate = serverFile.getLastWriteTime().toDate();
            String dateFormat = simpleDateFormat.format(lastUpdate);
            return localDate.toString().equals(dateFormat);
        });
    }

    /**
     * 从远处服务器下载到本地文件方法
     *
     * @param fileList    文件集合，用来存储文件
     * @param file        本地文件对象
     * @param smbFileRead 服务器文件对象
     * @param serverPath  服务器文件全路径，用来记录日志排查问题
     * @author lcy
     * @date 2021/5/18 12:42
     **/
    private static void downloadFile(List<File> fileList, File file, com.hierynomus.smbj.share.File smbFileRead, String serverPath) {
        try (FileOutputStream fos = new FileOutputStream(file);
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             InputStream inputStream = smbFileRead.getInputStream();
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
            byte[] bytes = new byte[20480];
            int read;
            while ((read = bufferedInputStream.read(bytes, 0, bytes.length)) != -1) {
                bos.write(bytes, 0, read);
            }
            smbFileRead.close();
            fileList.add(file);
        } catch (IOException e) {
            Log.error("文件下载失败。serverPath = {}", serverPath, e);
        }
    }

    /**
     * 通过smb协议写入文件
     *
     * @param smbInfo  smb配置类
     * @param file     文件
     * @param root     远程服务器root路径，没有文件符号
     * @param filePath 具体文件路径包含生成的文件名，开头不带符号
     * @author lcy
     * @date 2021/8/16 18:54
     **/
    public static void writeBySmb(SmbInfo smbInfo, File file, String root, String filePath) {
        //初始化配置
        SmbConfig smbConfig = initSmbConfig(smbInfo);
        try (SMBClient smbClient = new SMBClient(smbConfig)) {
            //获取连接
            Connection connection = smbClient.connect(smbInfo.getHost());
            AuthenticationContext ac = new AuthenticationContext(smbInfo.getUsername(), smbInfo.getPassword().toCharArray(), smbInfo.getDomain());
            Session session = connection.authenticate(ac);
            // 连接共享文件夹
            DiskShare share = (DiskShare) session.connectShare(root);
            System.out.println("connect smbj success");

            //配置写入参数
            Set<AccessMask> accessMasks = new HashSet<>();
            accessMasks.add(AccessMask.FILE_ADD_FILE);
            Set<FileAttributes> attributes = new HashSet<>();
            attributes.add(FileAttributes.FILE_ATTRIBUTE_NORMAL);
            Set<SMB2ShareAccess> smb2ShareAccesses = new HashSet<>();
            smb2ShareAccesses.add(SMB2ShareAccess.FILE_SHARE_WRITE);
            Set<SMB2CreateOptions> smb2CreateOptions = new HashSet<>();
            smb2CreateOptions.add(SMB2CreateOptions.FILE_RANDOM_ACCESS);

            com.hierynomus.smbj.share.File openFile = share.openFile(filePath, accessMasks, attributes, smb2ShareAccesses, SMB2CreateDisposition.FILE_OVERWRITE_IF, smb2CreateOptions);
            try (OutputStream oStream = openFile.getOutputStream();
                 BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = bufferedInputStream.read(buffer, 0, buffer.length)) != -1) {
                    oStream.write(buffer, 0, len);
                }
            }
            System.out.println("upload file success");
        } catch (IOException e) {
            Log.error("上传文件到服务器失败，host={},root={},path={}", smbInfo.getHost(), root, filePath);
            e.printStackTrace();
        }
    }

}
